/**
 * Copyright (c) 2007-2008 Nearchos Paspallis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * AspectSense
 *
 * Middleware for annotation-based, context-aware and adaptive services for
 * mobile and ubiquitous computing
 *
 * This project is an open-source effort, initiated by Nearchos Paspallis as
 * part of his PhD work at the University of Cyprus (http://www.ucy.ac.cy).
 *
 * More information about the project is available at "http://aspectsense.org".
 */

package org.aspectsense.graphs;

import java.util.Set;
import java.util.HashSet;

/**
 * Immutable implementation of a graph delta-data structure. It can be used to
 * inquire the differences between a source and a target {@link Graph}. Both
 * the source and the target graphs are {@link Bipartite}.
 *
 * @author Nearchos Paspallis [nearchos@cs.ucy.ac.cy]
 *         Date: Dec 27, 2007
 *         Time: 7:55:35 PM
 */
public class BipartiteGraphDelta<E>
{
    private final Set<Node<E>> sameLeftNodes;
    private final Set<Node<E>> sameRightNodes;
    private final Set<Node<E>> addedLeftNodes;
    private final Set<Node<E>> addedRightNodes;
    private final Set<Node<E>> removedLeftNodes;
    private final Set<Node<E>> removedRightNodes;

    private final Set<DirectedEdge<E>> sameEdges;
    private final Set<DirectedEdge<E>> addedEdges;
    private final Set<DirectedEdge<E>> removedEdges;

    public BipartiteGraphDelta(
            final Set<Node<E>> sameLeftNodes,
            final Set<Node<E>> sameRightNodes,
            final Set<Node<E>> addedLeftNodes,
            final Set<Node<E>> addedRightNodes,
            final Set<Node<E>> removedLeftNodes,
            final Set<Node<E>> removedRightNodes,
                      final Set<DirectedEdge<E>> sameEdges,
                      final Set<DirectedEdge<E>> addedEdges,
                      final Set<DirectedEdge<E>> removedEdges)
    {
        super();

        this.sameLeftNodes = new HashSet<Node<E>>(sameLeftNodes);
        this.sameRightNodes = new HashSet<Node<E>>(sameRightNodes);
        this.addedLeftNodes = new HashSet<Node<E>>(addedLeftNodes);
        this.addedRightNodes = new HashSet<Node<E>>(addedRightNodes);
        this.removedLeftNodes = new HashSet<Node<E>>(removedLeftNodes);
        this.removedRightNodes = new HashSet<Node<E>>(removedRightNodes);

        this.sameEdges = new HashSet<DirectedEdge<E>>(sameEdges);
        this.addedEdges = new HashSet<DirectedEdge<E>>(addedEdges);
        this.removedEdges = new HashSet<DirectedEdge<E>>(removedEdges);
    }

    public Set<Node<E>> getSameNodes()
    {
        final HashSet<Node<E>> allSameNodes = new HashSet<Node<E>>();
        allSameNodes.addAll(sameLeftNodes);
        allSameNodes.addAll(sameRightNodes);

        return allSameNodes;
    }

    public Set<Node<E>> getSameLeftNodes()
    {
        return sameLeftNodes;
    }

    public Set<Node<E>> getSameRightNodes()
    {
        return sameRightNodes;
    }

    public Set<Node<E>> getAddedNodes()
    {
        final HashSet<Node<E>> allAddedNodes = new HashSet<Node<E>>();
        allAddedNodes.addAll(addedLeftNodes);
        allAddedNodes.addAll(addedRightNodes);

        return allAddedNodes;
    }

    public Set<Node<E>> getAddedLeftNodes()
    {
        return addedLeftNodes;
    }

    public Set<Node<E>> getAddedRightNodes()
    {
        return addedRightNodes;
    }

    public Set<Node<E>> getRemovedNodes()
    {
        final HashSet<Node<E>> allRemovedNodes = new HashSet<Node<E>>();
        allRemovedNodes.addAll(removedLeftNodes);
        allRemovedNodes.addAll(removedRightNodes);

        return allRemovedNodes;
    }

    public Set<Node<E>> getRemovedLeftNodes()
    {
        return removedLeftNodes;
    }

    public Set<Node<E>> getRemovedRightNodes()
    {
        return removedRightNodes;
    }

    public Set<DirectedEdge<E>> getSameEdges()
    {
        return new HashSet<DirectedEdge<E>>(sameEdges);
    }

    public Set<DirectedEdge<E>> getAddedEdges()
    {
        return new HashSet<DirectedEdge<E>>(addedEdges);
    }

    public Set<DirectedEdge<E>> getRemovedEdges()
    {
        return new HashSet<DirectedEdge<E>>(removedEdges);
    }

    private static String lastPart(final Object object)
    {
        final String string = object.toString();

        int index = string.lastIndexOf('.');
        if(index == -1)
        {
            return string;
        }
        else
        {
            return string.substring(index + 1);
        }
    }

    public String toString()
    {
        final StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append("delta:\nLeft Nodes(\n/{");

        {
            final int numOfSameLeftNodes = sameLeftNodes.size();
            int slnCount = 0;
            for(final Node<E> node : sameLeftNodes)
            {
                slnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(slnCount < numOfSameLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedLeftNodes = addedLeftNodes.size();
            int alnCount = 0;
            for(final Node<E> node : addedLeftNodes)
            {
                alnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(alnCount < numOfAddedLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedLeftNodes = removedLeftNodes.size();
            int rlnCount = 0;
            for(final Node<E> node : removedLeftNodes)
            {
                rlnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(rlnCount < numOfRemovedLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("})\nRight Nodes(\n/{");

        {
            final int numOfSameRightNodes = sameRightNodes.size();
            int srnCount = 0;
            for(final Node<E> node : sameRightNodes)
            {
                srnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(srnCount < numOfSameRightNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedRightNodes = addedRightNodes.size();
            int arnCount = 0;
            for(final Node<E> node : addedRightNodes)
            {
                arnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(arnCount < numOfAddedRightNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedNodes = removedRightNodes.size();
            int rrnCount = 0;
            for(final Node<E> node : removedRightNodes)
            {
                rrnCount++;
                stringBuilder.append(lastPart(node.getValue()));
                if(rrnCount < numOfRemovedNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("})\nEdges(\n/{");

        {
            final int numOfSameEdges = sameEdges.size();
            int seCount = 0;
            for(final DirectedEdge<E> directedEdge: sameEdges)
            {
                seCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(lastPart(fromNode.getValue())).append("->").append(lastPart(toNode.getValue()));
                if(seCount < numOfSameEdges) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedEdges = addedEdges.size();
            int aeCount = 0;
            for(final DirectedEdge<E> directedEdge: addedEdges)
            {
                aeCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(lastPart(fromNode.getValue())).append("->").append(lastPart(toNode.getValue()));
                if(aeCount < numOfAddedEdges) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedEdges = removedEdges.size();
            int reCount = 0;
            for(final DirectedEdge<E> directedEdge: removedEdges)
            {
                reCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(lastPart(fromNode.getValue())).append("->").append(lastPart(toNode.getValue()));
                if(reCount < numOfRemovedEdges) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("})\n");

        return stringBuilder.toString();
    }

    public String toLongString()
    {
        final StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append("delta: Left Nodes(\n/{");

        {
            final int numOfSameLeftNodes = sameLeftNodes.size();
            int slnCount = 0;
            for(final Node<E> node : sameLeftNodes)
            {
                slnCount++;
                stringBuilder.append(node.getValue());
                if(slnCount < numOfSameLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedLeftNodes = addedLeftNodes.size();
            int alnCount = 0;
            for(final Node<E> node : addedLeftNodes)
            {
                alnCount++;
                stringBuilder.append(node.getValue());
                if(alnCount < numOfAddedLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedLeftNodes = removedLeftNodes.size();
            int rlnCount = 0;
            for(final Node<E> node : removedLeftNodes)
            {
                rlnCount++;
                stringBuilder.append(node.getValue());
                if(rlnCount < numOfRemovedLeftNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\nRight Nodes(\n/{");

        {
            final int numOfSameRightNodes = sameRightNodes.size();
            int srnCount = 0;
            for(final Node<E> node : sameRightNodes)
            {
                srnCount++;
                stringBuilder.append(node.getValue());
                if(srnCount < numOfSameRightNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedRightNodes = addedRightNodes.size();
            int arnCount = 0;
            for(final Node<E> node : addedRightNodes)
            {
                arnCount++;
                stringBuilder.append(node.getValue());
                if(arnCount < numOfAddedRightNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedNodes = removedRightNodes.size();
            int rrnCount = 0;
            for(final Node<E> node : removedRightNodes)
            {
                rrnCount++;
                stringBuilder.append(node.getValue());
                if(rrnCount < numOfRemovedNodes) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n/{");

        {
            final int numOfSameEdges = sameEdges.size();
            int seCount = 0;
            for(final DirectedEdge<E> directedEdge: sameEdges)
            {
                seCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(fromNode.getValue()).append("->").append(toNode.getValue());
                if(seCount < numOfSameEdges) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n+{");

        {
            final int numOfAddedEdges = addedEdges.size();
            int aeCount = 0;
            for(final DirectedEdge<E> directedEdge: addedEdges)
            {
                aeCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(fromNode.getValue()).append("->").append(toNode.getValue());
                if(aeCount < numOfAddedEdges) stringBuilder.append(", ");
            }
        }

        stringBuilder.append("}\n-{");

        {
            final int numOfRemovedEdges = removedEdges.size();
            int reCount = 0;
            for(final DirectedEdge<E> directedEdge: removedEdges)
            {
                reCount++;
                final Node<E> fromNode = directedEdge.getFromNode();
                final Node<E> toNode = directedEdge.getToNode();

                stringBuilder.append(fromNode.getValue()).append("->").append(toNode.getValue());
                if(reCount < numOfRemovedEdges) stringBuilder.append(", ");
            }
        }

        return stringBuilder.toString();
    }
}